﻿// Copyright (c) 2010, James Killick
// http://code.google.com/p/kosher/
// All rights reserved.

// Redistribution and use in source and binary forms, with or without 
// modification, are permitted provided that the following conditions are met:

//     * Redistributions of source code must retain the above copyright notice, 
//       this list of conditions and the following disclaimer.
//     * Redistributions in binary form must reproduce the above copyright notice,
//       this list of conditions and the following disclaimer in the documentation
//       and/or other materials provided with the distribution.
//     * Neither the name of the Kosher team nor the names of its contributors may 
//       be used to endorse or promote products derived from this software without 
//       specific prior written permission.

// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
// ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
// WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 
// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 
// FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 
// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 
// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 
// CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 
// OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

using System;
using System.Linq;

namespace Kosher
{
    sealed class TestBuilder<T, TResult, TError> : IOtherwise<TResult, TError>
    {
        private readonly TestTarget<T, TResult, TError> _target;
        private TestGroup<TResult> _currentTestGroup;

        public TestTarget<T, TResult, TError> Target
        {
            get { return _target; }
        }

        public TestBuilder(Func<T, TResult> func, string key)
        {
            Guard.ArgumentNotNull(func, "func");
            Guard.ArgumentNotNullOrEmpty(key, "key");

            _target = new TestTarget<T, TResult, TError>()
                          {
                              Target = func,
                              Key = key
                          };
        }

        private void AddPredicate(Predicate<TResult> predicate)
        {
            if (_currentTestGroup == null)
            {
                _currentTestGroup = new TestGroup<TResult>();
            }

            _currentTestGroup.Tests.Add(predicate);
        }

        private void AddAction(Action action, bool abort)
        {
            _currentTestGroup.FailureAction = action;
            _currentTestGroup.CausesAbort = abort;
            _target.TestGroups.Add(_currentTestGroup);
            _currentTestGroup = null;
        }

        #region Implementation of ICondition<TResult,TError>

        /// <summary>
        /// Specifies a predicate to test a target of validation
        /// </summary>
        /// <param name="test">The predicate, e.g. aStringProperty => !string.IsNullOrEmpty(aStringProperty)</param>
        /// <returns>Fluent interface to continue building the validation test</returns>
        public IOtherwise<TResult, TError> IsTrue(Predicate<TResult> test)
        {
            Guard.ArgumentNotNull(test, "test");
            AddPredicate(test);
            return this;
        }

        #endregion

        #region Implementation of IOtherwise<TResult,TError>

        public ICondition<TResult, TError> Otherwise(TError err)
        {
            return Otherwise(err, false);
        }

        public ICondition<TResult, TError> Otherwise(Func<TError> func)
        {
            return Otherwise(func, false);
        }

        public ICondition<TResult, TError> Otherwise(TError err, bool abort)
        {
            Guard.ArgumentNotNull(err, "err");
            AddAction(() => _target.Errors.Add(err), abort);
            return this;
        }

        public ICondition<TResult, TError> Otherwise(Func<TError> func, bool abort)
        {
            Guard.ArgumentNotNull(func, "func");
            AddAction(() => _target.Errors.Add(func()), abort);
            return this;
        }

        public ICondition<TResult, TError> Otherwise(Func<KeyedErrorCollection<TError>> func)
        {
            return Otherwise(func, false);
        }

        public ICondition<TResult, TError> Otherwise(Func<KeyedErrorCollection<TError>> func, bool abort)
        {
            Guard.ArgumentNotNull(func, "func");
            AddAction(() =>
            {
                var errors = func();
                Guard.NotNull(errors, "errors");
                errors.ToList().ForEach(ec => ec.ToList().ForEach(e => _target.Errors.Add(e)));
            }, abort);
            return this;
        }

        #endregion
    }
}
